package org.weed.remote.network;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

import org.weed.remote.service.ConsoleService;
import org.weed.remote.service.DeviceService;
import org.weed.remote.service.DeviceServiceImpl;
import org.weed.remote.service.EventService;
import org.weed.remote.ui.RemoteMonitor;
import org.weed.remote.util.ByteUtils;

public class RemoteServer implements Runnable {

	private int port;
	private ServerSocketChannel server;
	private Charset charset = Charset.forName("ISO-8859-1");
	private CharsetDecoder decoder = charset.newDecoder();
	private CharsetEncoder encoder = charset.newEncoder();
	
	private boolean isCancelled;
	
	private ConsoleService console;
	private DeviceService robot;
	
	private ByteBuffer byteBuffer;
	
	private RemoteMonitor monitor;
	
	public RemoteServer(int port, ConsoleService console) throws Exception {
		byteBuffer = ByteBuffer.allocate(1);
		isCancelled = false;
		this.port = port;
		this.console = console;
		initServices();
		monitor = new RemoteMonitor();
	}
	
	private void initServices() throws Exception  {
		robot = new DeviceServiceImpl();
	}
	
	public void run() {
		isCancelled = true;
		try {
			server = ServerSocketChannel.open();
			server.bind(new InetSocketAddress(port));			
			console.info("Server Starting at port " + port);
			while(isCancelled) {
				SocketChannel client = server.accept();
				console.info(client.getRemoteAddress()+" is connected.");
				process(client);
				console.info("Client disconnected.");
			}
		} catch (Exception e) {
			console.info("Server stoped.");
		}
	}

	
	private void process(SocketChannel client) {
		while (true) {
			try {
				byte command = getByte(client);
				switch (EventService.getType(command)) {
				case EventService.TYPE_MONITOR:
					processMoniterEvent(client, command);
					break;
				case EventService.TYPE_MOUSE:
					processMouseEvent(client, command);
					break;
				default:
					console.error("Unknown command: " + ByteUtils.toHexString(command));
				}				
			} catch (Exception e) {
				break;
			}
		}
	}
	
	private void processMoniterEvent(SocketChannel client, byte command) throws Exception {
		byte[] by = getBytes(client, 4);
		ByteUtils.printBytes(by);
		int size = ByteUtils.toInt(by);
		System.out.println("Reading image size: " + size + " bytes...");
		byte[] bytes = getBytes(client, size);
		monitor.refresh(ByteUtils.toBufferedImage(bytes));
	}

	private void processMouseEvent(SocketChannel client, byte command) throws Exception {
		switch (EventService.getMouseEvent(command)) {
		case EventService.MOVE:
			int x = ByteUtils.toInt(getBytes(client, 4));
			int y = ByteUtils.toInt(getBytes(client, 4));
			robot.mouseMove(x, y);
			break;
		case EventService.PRESS:
			robot.mousePress(EventService.getMouseButton(command));
			break;
		case EventService.RELEASE:
			robot.mouseRelease();
			break;
		case EventService.CLICK:
			robot.mouseClick(EventService.getMouseButton(command));
			break;
		default:
			
		}
	}	

	
	private byte[] getBytes2(SocketChannel client, int count) throws Exception {
		byte[] bytes = new byte[count];
		for (int i = 0; i < count; i++) {
			bytes[i] = getByte(client);
		}
		return bytes;
	}
	
	private byte[] getBytes(SocketChannel client, int count) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		for (int i = 0; i < count; i++) {
			baos.write(new byte[] {getByte(client)});
			System.out.println(i+"/"+count);
		}
		return baos.toByteArray();
	}
	
	
	private byte getByte(SocketChannel client) throws Exception {
		byteBuffer.clear();
		if (client.read(byteBuffer) == -1) {
			throw new IOException("Lost connection");
		}
		byteBuffer.flip();
		return byteBuffer.get();
	}
	
	private void sendResponse(SocketChannel client) throws Exception {
		String message = "";
		client.write(encoder.encode(CharBuffer.wrap(message)));
	}
	
	public void terminate() {	
		console.info("Server terminating...");
		try {
			isCancelled = false;
			server.socket().close();
			server.close();
		} catch (Exception e) {
		}
	}
	
	public static void main(String[] args) throws Exception {
		RemoteServer remoteServer = new RemoteServer(8888, null);
		remoteServer.run();
	}
}
